/**
 * The Woosta Table Football Engine
 *
 * License... etc.
 **
 * Texture Module
 *
 * Provides:
 * 	- basic texture class
 *  - FBO Textures
 *  - image texture class (both square and rectangular)
 *  - automatic mipmap generation and setup for square textures
 **
 * Author: Sean Micklethwaite, Mac Duy Hai
 **/

#include "texture.h"
#include "glext.h"
#include "graphics_functions.h"
#include <corona.h>

using namespace WtfEngine;

GLuint	FBOTexture::gFBO = NULL;


Texture::Texture(const StringPool::Entry& sName) : DataObject(sName), mHandle(NULL) {
	// square textures have been implemented after rectangular ones
	// so all textures start as rectangular.
	rectangular = true;
};

Texture::~Texture(){
};


/**
 * Loads an image as a texture and depending on the dimensions of the image,
 * sets the texture to square or rectangular type. ForceRectangular forces
 * the texture mode to be rectangular in any case.
 */
void Texture::Read(const String& sImageFile, bool forceRect)
{
	// load image
	corona::Image*	pImage = corona::OpenImage(sImageFile.c_str(),
		corona::PF_R8G8B8A8);
	if(!pImage){
		throw String("Failed to load texture image: ") + sImageFile;
	};

	// Flip the image (corona loads upside down to OpenGL)
	// XXX: Textures seem to need to be upside down. What's going on?
	//corona::FlipImage(pImage, corona::CA_X);

	// load dimensions and set mode (rect/square)
	int texture_mode = 0;
	mDim[0] = pImage->getWidth();
	mDim[1] = pImage->getHeight();
	if (!forceRect && (mDim[0] == mDim[1])) {
		this->rectangular = false;
		texture_mode = GL_TEXTURE_2D;
	} else {
		texture_mode = GL_TEXTURE_RECTANGLE_NV;
	}

	// bind and load to gfx mem - and enable filtering
	glGenTextures(1, &mHandle);
	Bind();
	glTexParameteri(texture_mode, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

	if (texture_mode == GL_TEXTURE_2D) {
		// for square textures, also generate mipmaps
		glTexParameterf(texture_mode, GL_TEXTURE_WRAP_S, GL_REPEAT );
		glTexParameterf(texture_mode, GL_TEXTURE_WRAP_T, GL_REPEAT );
		glTexParameteri(texture_mode, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_LINEAR);
		
		gluBuild2DMipmaps(texture_mode, GL_RGBA8, pImage->getWidth(), pImage->getHeight(),
						  GL_RGBA, GL_UNSIGNED_BYTE, pImage->getPixels());
	} else {
		glTexParameteri(texture_mode, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
		glTexImage2D(texture_mode, 0, GL_RGBA32F_ARB, pImage->getWidth(),
					 pImage->getHeight(), 0,	GL_RGBA, GL_UNSIGNED_BYTE, pImage->getPixels());
	}
	Release();

	// clean up
	delete pImage;
	checkGLError("Loaded Texture Image");
};


void Texture::Write(const String& sFile) {
	throw new std::logic_error("Writing texture to file not supported");
};

void Texture::Create(const WtfGraphics::IPixelBuffer::tRef& rPixels, const iVector2D& vDim) {
	throw new std::logic_error("Texture create - not implemented");
};

void Texture::Draw(const iVector2D& vSize) {
	Bind();

	// texture coordinates depend on type of texture
	if (rectangular) {
		glBegin(GL_QUADS);
			glTexCoord2d(0, 0);
			glVertex3d(0, 0, 0.0);
			glTexCoord2d(mDim[0], 0);
			glVertex3d(vSize[0], 0, 0.0);
			glTexCoord2d(mDim[0], mDim[1]);
			glVertex3d(vSize[0], vSize[1], 0);
			glTexCoord2d(0, mDim[1]);
			glVertex3d(0, vSize[1], 0.0);
		glEnd();
	} else {
		glBegin(GL_QUADS);
			glTexCoord2d(0, 0);
			glVertex3d(0, 0, 0.0);
			glTexCoord2d(1, 0);
			glVertex3d(vSize[0], 0, 0.0);
			glTexCoord2d(1, 1);
			glVertex3d(vSize[0], vSize[1], 0);
			glTexCoord2d(0, 1);
			glVertex3d(0, vSize[1], 0.0);
		glEnd();
	};

	Release();
};

/// Draws the texture to specified screen
void Texture::DrawToScreen(const IScreen::tRef& rScreen)
{
	WtfGraphics::SaveProjection();
	WtfGraphics::SetupOrthoProjection(rScreen);
	WtfGraphics::ClearScreen();

	Draw(rScreen->getSize());
	
	WtfGraphics::RestoreProjection();
};

/// Binds texture to gfx mem
void Texture::Bind(int texNum)
{
	glActiveTextureARB(GL_TEXTURE0 + texNum);
	glClientActiveTexture(GL_TEXTURE0_ARB + texNum);
	checkGLError("setting texture number");
	
	if (rectangular) {
		glEnable(GL_TEXTURE_RECTANGLE_NV);
		glBindTexture(GL_TEXTURE_RECTANGLE_NV, mHandle);
	} else {
		glEnable(GL_TEXTURE_2D);
		glBindTexture(GL_TEXTURE_2D, mHandle);
	}
	checkGLError("binding texture");
};

/// Release the texture from mem
void Texture::Release(int texNum)
{
	glActiveTextureARB(GL_TEXTURE0 + texNum);

	if (rectangular) {
		glDisable(GL_TEXTURE_RECTANGLE_NV);
		glBindTexture(GL_TEXTURE_RECTANGLE_NV, 0);
	} else {
		glDisable(GL_TEXTURE_2D);
		glBindTexture(GL_TEXTURE_2D, 0);
	}
	checkGLError("releasing texture");
};

WtfEngine::IDataObject * Texture::Load(const StringPool::Entry& sImageFile)
{
	TextureImage * pTex = new TextureImage(sImageFile);
	pTex->Read(*sImageFile, true);
	return pTex;
};


/**
 * FBOTexture Class
 */
FBOTexture::FBOTexture()
	: mDepthBuf(NULL), Texture(StringPool::GetInstance()["FBOTexture"]) {};

FBOTexture::~FBOTexture()
{
	if(mDepthBuf){
		//glDeleteFramebuffersEXT(1, &mFBO); TODO! refcount this
		glDeleteRenderbuffersEXT(1, &mDepthBuf);
	};
};

void FBOTexture::Create(int w, int h)
{
	// Generate depth buffer
	glGenRenderbuffersEXT(1, &mDepthBuf);
	glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, mDepthBuf);
	glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH24_STENCIL8_EXT,
		w, h);
	checkGLError("creating FBO depth buffer");
	glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, 0);

	// Generate texture proper
	glGenTextures(1, &mHandle);
	glBindTexture(GL_TEXTURE_RECTANGLE_NV, mHandle);
	glTexImage2D(GL_TEXTURE_RECTANGLE_NV, 0, GL_RGBA32F_ARB, // HDR format
		w, h, 0, GL_RGBA, GL_FLOAT, NULL);
	checkGLError("creating FBO texture");
	glTexParameteri(GL_TEXTURE_RECTANGLE_NV, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_RECTANGLE_NV, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	checkGLError("setting FBO texture parameters");
	glBindTexture(GL_TEXTURE_RECTANGLE_NV, 0);

	
	// Create FBO, if not yet existent
	if(!gFBO){
		glGenFramebuffersEXT(1, &gFBO);
		checkGLError("generating FBO");
	};
	
	assert(gFBO != 0);
	glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, gFBO);
	
	// Attach to FBO
	glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
							  GL_TEXTURE_RECTANGLE_NV, mHandle, 0);
	checkGLError("attaching to FBO texture (init)");
	glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT,
								 GL_RENDERBUFFER_EXT, mDepthBuf);
	checkGLError("attaching to FBO depth buffer (init)");
	glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_STENCIL_ATTACHMENT_EXT,
		GL_RENDERBUFFER_EXT, mDepthBuf);
	checkGLError("attaching to FBO stencil buffer (init)");

	GLenum status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);

	if(status != GL_FRAMEBUFFER_COMPLETE_EXT)
		throw "FBOTexture: failed to initialise render buffers";

	mDim[0] = w; mDim[1] = h;

	glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
};

void FBOTexture::Write(const String& sFile) {
	// Create pixel buffer to hold pixel data
	LocalObject<WtfGraphics::PixelType::tRGBA8> rPixels(WtfGraphics::PixelType::tRGBA8(getWidth() * getHeight()));
	
	// Read into pixel buffer
	Activate();

	glReadBuffer(GL_COLOR_ATTACHMENT0_EXT);
	checkGLError("setting read buffer");
	glReadPixels(0,0,mDim.x(),mDim.y(),GL_RGBA,GL_UNSIGNED_BYTE, rPixels->getPixels());
	checkGLError("reading texture");

	Deactivate();

	// Save the image
	corona::Image * pImage = corona::CreateImage(getWidth(), getHeight(), corona::PF_R8G8B8A8, rPixels->getPixels());
	//corona::FlipImage(pImage, corona::CA_X);
	corona::SaveImage(sFile.c_str(), corona::FF_AUTODETECT, pImage);
	delete pImage;
};

void FBOTexture::Read(const tPixels::tRef& rPixels)
{
	// Attach to FBO
	Activate();

	glReadBuffer(GL_COLOR_ATTACHMENT0_EXT);
	checkGLError("setting read buffer");
	glReadPixels(0,0,mDim.x(),mDim.y(),GL_RGBA,GL_FLOAT,rPixels->getPixels());
	checkGLError("reading texture");

	Deactivate();
};

void FBOTexture::Write(const tPixels::tRef& rPixels)
{
	// Attach to FBO
	Activate();

	glDrawBuffer(GL_COLOR_ATTACHMENT0_EXT);
	glDrawPixels(mDim.x(),mDim.y(),GL_RGBA,GL_FLOAT,rPixels->getPixels());
	checkGLError("writing texture");

	Deactivate();
};

void FBOTexture::Activate()
{
	// Activate FBO
	glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, gFBO);
	checkGLError("activating FBO");

	// Attach to FBO
	glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
		GL_TEXTURE_RECTANGLE_NV, mHandle, 0);
	glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT,
		GL_RENDERBUFFER_EXT, mDepthBuf);
	glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_STENCIL_ATTACHMENT_EXT,
		GL_RENDERBUFFER_EXT, mDepthBuf);
};

void FBOTexture::Deactivate()
{
	// Disable FBO
	glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
	checkGLError("disabling FBO");
};


